import axios, {AxiosRequestConfig} from "axios"
import qs from 'qs'
import JSEncrypt from 'jsencrypt'
import CryptoJS from 'crypto-js'


function transformRequestData(data){
    if(typeof data === 'object')
        return qs.stringify(data)
    else
        return data
}

let baseUrls= {
    dev: '/api',
    prod: '/'
}

const http = axios.create({
    baseURL: baseUrls[import.meta.env.MODE],
    // timeout: 500,
    withCredentials: true,
    transformRequest: (data, headers) => transformRequestData(data),
    paramsSerializer: (data) =>  transformRequestData(data)
})

const attrSessionName = 'attr'

let attr = {
    str:{
        sSymKey: '',
        sPubKey: '',
        sSymIv: ''
    },
    symKey: null,
    asymAttr: 'enc_data_pre',
    symAttr: 'enc_data',
    asymEncrypt: new JSEncrypt()
}

function randomString(len) {
    len = len || 32;
    let chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';
    let maxPos = chars.length;
    let pwd = '';
    for (let i = 0; i < len; i++) {
         pwd += chars.charAt(Math.floor(Math.random() * maxPos));
    }　　
    return pwd;
    }

function createSymKey(refresh = false) {
    if(refresh || !attr.str.sSymKey){
        attr.str.sSymKey = randomString(16);
        attr.str.sSymIv = randomString(16)
        attr.symKey = CryptoJS.enc.Utf8.parse(attr.str.sSymKey);
    }
    return new Promise(((resolve, reject) => {
        sendSymKey().then(value => {
            resolve(value)
        })
    }))

}

function getAsymKey(refresh=false){
    return new Promise((resolve, reject) => {
        if (refresh||!attr.str.sSymKey){
            http.get("/auth/pub_key").then((resp)=>{
                if (resp.data.code === 200){
                    const pubKey = resp.data['data']['pub_key']
                    attr.asymEncrypt = new JSEncrypt();
                    attr.asymEncrypt.setPublicKey(pubKey)
                    attr.str.sPubKey = pubKey
                    resolve(pubKey)
                }else{
                    
                    reject(resp)
                }
            });
        }else{
            resolve(attr.asymAttr);
        }
    })
}


/**
 *
 */
let tryTime = 0
async function sendSymKey(){
    tryTime ++
    let send = {}
    send[attr.asymAttr] = encDataWithPubKey(JSON.stringify({
            sym_key: attr.str.sSymKey,
            iv: attr.str.sSymIv,
            pub: "pub"}))
    return new Promise(((resolve, reject) => {
        http.post('/auth/sym_key', send).then((res)=>{
            if (res.data.code === 200){
                let testEnc = res.data.data['test_data']
                let success = false;
                try {
                    decDataWithSymKey(testEnc);
                    success = true;
                    tryTime = 0;
                    console.log('成功设置对话密钥' + res.data.msg);
                    sessionStorage.setItem(attrSessionName, JSON.stringify(attr.str));
                }catch (e) {
                    console.warn(e)
                }
                resolve(res)
            }else if (res.data.code === 450){ // 密钥设置失败
                console.log('设置对话密钥失败' + res.data.msg)
                if (tryTime < 5){
                    getAsymKey(true).then((value => {  // 重新获取服务端公钥
                        createSymKey(true)  // 重新生成密钥
                    }))
                }
            }
        }).catch(reason => {
            if (reason.code === 450){
                console.log('密钥解析失败' + reason.data.msg)
                getAsymKey(true).then((value => {  // 重新获取服务端公钥
                    createSymKey(true)  // 重新生成密钥
                }))
            }
            reject(reason)
            console.log("设置密钥失败" + reason)
        })
    }))
}

/**
 * 使用公钥加密数据
 * @param data
 * @return {string}
 */
function encDataWithPubKey(data=''){
    return  attr.asymEncrypt.encrypt(data);
}

/**
 * 使用对称密钥加密数据
 * @type string
 * @return string 加密后使用base64编码的数据
 */
function encDataWithSymKey(data = ''){
    let iv = CryptoJS.enc.Utf8.parse(attr.str.sSymIv);
    let encrypted = CryptoJS.AES.encrypt(data, attr.symKey, {
        iv: iv,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.ZeroPadding
    });
    // 转换为base64字符串
    return encrypted.toString()
}

/**
 *
 * @param data 此处数据必须为原始数据的字节经过base64编码后的结果
 * @return {string} 解密后的数据
 */
function decDataWithSymKey(data = '') {
    console.log('解密前：' + data)
    let iv = CryptoJS.enc.Utf8.parse(attr.str.sSymIv);
    let decrypted = CryptoJS.AES.decrypt(data, attr.symKey, {mode:CryptoJS.mode.CBC,padding: CryptoJS.pad.ZeroPadding,
        iv: iv})
    decrypted = CryptoJS.enc.Utf8.stringify(decrypted);
    console.log('解密后：' + decrypted)
    return decrypted
}

function requestDataWithSymEnc(url, method, data, conf=null ) {
    return new Promise(((resolve, reject) => {
        if (typeof data === 'object'){
            data = JSON.stringify(data);
        }
        let send = {}
        send[attr.symAttr] = encDataWithSymKey(data)
        let success = false
        let config = {url, method, data:send, ...conf}
        http.request(config).then((res)=>{
            if (res.data.code !== 450){
                success = true
            }
            resolve(res);
        }).catch((reason => {
            let response = reason.response
            if (response.data.status!==450)
                success = true;
            reject(reason);
        })).finally(()=>{
            if (success!==true){
                createSymKey(true);
            }
        })
    }))
}

function postDataWithSymEnc(url, data, conf = null  ) {
    return requestDataWithSymEnc(url, 'post', data, conf);
}

function putDataWithSymEnc(url, data, conf) {
    return requestDataWithSymEnc(url,'put', data, conf);
}

function restoreAttr(data) {
    if (typeof data ==='string'){
        data = JSON.parse(data)
    }
    // 恢复公钥
    attr.asymEncrypt = new JSEncrypt();
    attr.asymEncrypt.setPublicKey(attr.str.sPubKey)
    // 恢复对称密钥
    attr.symKey = CryptoJS.enc.Utf8.parse(attr.str.sSymKey);

}

function init(refresh = false){
    if (refresh || !sessionStorage.getItem(attrSessionName)){  // 未存储密钥数据
        getAsymKey(true).then((value => {
            createSymKey(true).then((value1 => {
            }))
        }))
    }else{
        // 恢复密钥数据
        restoreAttr(sessionStorage.getItem(attrSessionName))
    }
}

http['postDataWithSymEnc'] = postDataWithSymEnc
http['putDataWithSymEnc'] = putDataWithSymEnc
http['init'] = init
init(false)
export default http

